Skip to content

Implement progression archetype system and enhance Discover gallery#22

Merged
rondlite merged 286 commits into
trunkfrom
feat/charluv-rewrite
Jun 29, 2026
Merged

Implement progression archetype system and enhance Discover gallery#22
rondlite merged 286 commits into
trunkfrom
feat/charluv-rewrite

Conversation

@rondlite

Copy link
Copy Markdown
Contributor

No description provided.

rondlite and others added 30 commits June 20, 2026 04:22
…n speed

Edit page (ourdream-style):
- remove tab system -> single scrolling page
- fixed W++ trait set (no format picker / dynamic attributes); reuse
  appearance/gender/age, add species/personality/mind/likes/dislikes/background
- read-only warning for non-standard W++ attributes (dropped on save)
- remove Voice + Advanced sections
- creator auto-set to user handle; characterVersion auto-increments

Image:
- gallery max 10, pick up to 4 for the LoRA; "Build LoRA" encodes via Z-Image
  and stores loraName on the character
- server-side asset->base64 resolution (SSRF-guarded, avoids CDN CORS)
- native generate_image tool: model can request an image mid-chat -> Z-Image
  with the character's LoRA, appended to the reply
- resolve the chat character for every image so the LoRA always applies
- size by source: chat 512, character/avatar 768

Progression:
- per-character speed (slow/normal/fast) -> XP per message 1/3/7

Other:
- summaries no longer charge credits or advance XP
- plain/inference prompts sent as user role (endpoint requires a user message)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
Character traits:
- align fixed persona traits to the canonical Charluv W++ keys (species, mind,
  personality, age, job, description, sexuality, likes, loves, zodiac, hates,
  country, body, appearance) so real characters round-trip instead of being
  dropped as "extras"
- gender moves out of the persona into charluv metadata (char.gender); art style
  is derived from tags (anime/realistic)
- inject charluv metadata (gender, age range) into the prompt at chat time so the
  model still sees facets that aren't in the W++ persona

Gallery:
- re-seed the gallery from the saved character once it loads (was captured empty
  at mount, so images vanished on reopen / only appeared after adding one)
- readAssetBase64 reads directly from S3/R2 when storage is enabled, so the
  Build-LoRA encode can resolve gallery URLs regardless of ASSET_URL/CDN config

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
- Discover card click now opens a character profile/preview page
  (/discover/:id) with the gallery, age, description, facets and an editable
  name field instead of instantly matching
- "Match & Start Chat" clones the template with the chosen name, creates a chat
  directly and jumps to /chat/:id — the create-chat form step is bypassed
- server: POST /match/:id honors an optional name; GET /match/:id returns the
  template character for the profile page (registered after /discover)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
- Create wizard rebuilt as an ourdream-style image-card flow: gender/style/age,
  ethnicity + skin tone, hair + hair/eye color, body/breast/butt, personality,
  finish. Step-icon progress, two-tone hero, color swatches for skin/hair/eyes.
- Option previews load from /assets/wizard/...; appearance groups are gender +
  style aware: /assets/wizard/<style>/<gender>/<group>/<slug>.png. Missing
  assets degrade to labelled placeholders.
- Profile page: large two-tone "Name, Age" title (name live-editable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
- srv/memory: server-side embeddings via @xenova/transformers (all-MiniLM-L6-v2,
  lazy in-process) and a Mongo-backed vector store with brute-force cosine
  retrieval, scoped per user+character (persists across chats)
- native `remember` tool: the model durably saves facts about the user/relationship;
  stored on the assistant turn via the existing tool-call meta channel
- RAG: recall top-k relevant memories from the recent turns and inject them into
  the {{memory}} prompt slot at generation time
- replaces memory books; backend swappable for pgvector/endpoint later

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
Root cause: image persistence was gated on the SAVE_IMAGES flag (unset), so chat
images were uploaded to R2 but never attached to a chat message — they showed
live over WS then vanished from history on refresh (orphaning the R2 object).

- persist whenever the image belongs to a chat (has a chatId), regardless of
  SAVE_IMAGES; the flag still forces saving for non-chat images
- the native image tool now creates its OWN image message (parented to the reply)
  instead of appending onto the text turn — appending set adapter:'image' on the
  text message, which the renderer treats as an image filename and would blank
  the reply text

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
…tics

- memory logs now carry only metadata (length/count/topScore); the actual text
  is gated behind MEMORY_DEBUG_LOG_TEXT=1 (dev only)
- openai adapter: debug-log when tools are attached and whether the model
  returned any tool_calls (finish_reason + names) to diagnose tool-calling

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
…he field)

The single-page editor dropped the culture input, but the form guard still
required culture:'string', so getStrictForm rejected save/create with
"Request body is invalid: .culture is undefined".

- newCharGuard: culture -> optional
- getPayload: default culture from state / defaultCulture
- create wizard: set culture 'en-us' on the payload

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
Native function-calling doesn't fire on the charluv finetune: the prompt
prefills the assistant turn (the "<name>:" steering), so the model generates
text mid-turn and can't emit tool_calls (logs showed finish_reason:stop,
toolCallCount:0 with tools correctly attached + qwen3_xml parser).

Switch to in-band markers the model writes inside its reply:
  <image>visual description</image>   -> fires Z-Image (char LoRA)
  <remember>fact</remember>          -> stores long-term memory
Server parses + strips them from the reply and feeds the existing downstream
(meta.imageTool / meta.rememberFacts). Markers are also stripped from streaming
partials so raw tags never show. Instructions are prepended to the system
message when the features are available.

Native OpenAI tools/tool_calls remain behind CHARLUV_NATIVE_TOOLS=1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
The dream-date wizard had no way to set an image. Added a "Portrait (optional)"
section on the Finish step:
- Generate from choices: text-to-image via Z-Image using a prompt built from the
  selected style/ethnicity/gender/appearance (no LoRA yet)
- Upload: pick an image file
The chosen image is set as the new character's avatar in the create payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
Reword the marker instructions from a meta "you have special abilities / the
system processes tags" into an in-world platform capability ("on Charluv, every
companion can share photos and remember things"). Characters are far more likely
to use the tags in-character; the previous framing was easy for a restrictive
persona to refuse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
- Dockerfile: pnpm rebuild sharp — pnpm v10 skips dependency build scripts, so
  @xenova/transformers' sharp native binary was missing, breaking long-term
  memory ("Cannot find module sharp-linux-x64.node"). Image gen was fine (remote
  Z-Image); only the embedder failed.
- Marker instructions made more selective: at most one <image> per reply and only
  when fitting; <remember> only for genuinely important facts — the model was
  emitting both on nearly every turn. Also told not to narrate doing it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
Both are superseded:
- memory books -> long-term memory (RAG) now fills {{memory}} server-side; the
  keyword-book injection is removed (it was leaking the old character_book, e.g.
  "Julia's cat...", into unrelated chats)
- attached scenario books -> relationship progression (the LEVEL token). Only the
  character's initial scenario (or the chat's overridden scenario) remains, plus
  the progression token + charluv meta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
The native image tool created a detached image message that popped in with no
feedback. Now it behaves like normal in-chat image gen:
- server attaches the image to the reply (messageId + append) and no longer sets
  adapter:'image' on append (preserved adapter, so the reply text still renders)
- image-generation-started is broadcast to chat members with the messageId
- client shows a Spinner on the reply while generating (imagesGenerating state),
  renders the attached image from extras under the text, clears on message-retry

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
`pnpm rebuild sharp` silently no-ops under pnpm v10's build-script gate, so the
native binary was still missing. Run sharp's own install script directly in the
pnpm store path to fetch the prebuilt binary, unblocking the memory embedder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
- One "Images" card: cover (avatar) + gallery in a single grid; every tile
  (incl. the avatar) is selectable for the LoRA; Generate / Upload / Build LoRA /
  Make-cover / Remove. Server: encode-lora allows the avatar ref; new
  POST /character/:id/cover sets the avatar from an existing image.
- Gallery "Generate" now composes a real prompt from the persona
  (appearance/body/species/age + name) instead of the empty appearance field.
- Sprite feature removed from the editor UI (schema field kept, no migration;
  original sprite data forwarded on save).
- LoRA re-encode reuses the character's existing loraName so it REPLACES the old
  LoRA instead of minting a new uuid each time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
…22 bookworm

- remove dead deps: all @parcel/* + parcel (Vite is the bundler), localtunnel +
  @types/localtunnel (dev tunnel); drop their npm scripts and the localtunnel
  usage in srv/start.ts, srv/bin.ts, srv/config.ts; drop .parcelrc from Docker
- add pnpm.overrides (33) pinning patched transitive deps (protobufjs, pbkdf2,
  sha.js, fast-xml-parser, shell-quote, undici, esbuild, rollup, qs, ws, ...) —
  clears all 6 criticals + most highs
- bump dompurify, exifreader, js-cookie, svgo, vite to patched
- Dockerfile: node:22.14.0-bullseye-slim -> node:22-bookworm-slim

Remaining 9 are justified (no patch / major-break / non-exploitable):
pdfjs-dist+webpack (v4 layout break), vite (dev-server only, v6 major),
uuid (v4-only usage, advisory is v3/5/6), showdown/elliptic (no patch).
discord.js kept — still imported (Discord rip is a later phase).

srv build:server exit 0; web tsc unchanged (790); frozen-lockfile OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
…build)

The `>=7.29.6` override resolved to Babel 8.0.1, which is incompatible with
babel-preset-solid and crashed `vite build` in CI. Pin to `^7.29.6` (resolves
7.29.7) — still patched, builds clean (verified locally, 1m42s).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
The remaining 9 advisories cannot be fixed without breaking the build or removing
a kept feature, or are non-exploitable here. Recorded them in
pnpm.auditConfig.ignoreGhsas with per-advisory justification in
SECURITY-ACCEPTED.md, so `pnpm audit` now exits 0 and each is tracked.

- no upstream patch: elliptic, showdown
- non-exploitable + fix breaks CJS build: uuid (v4-only usage)
- dev-server/Windows-only, fix needs Vite 6 (breaks node-polyfills build): vite x3
- pdfjs-dist high + its webpack peer x2: v4 ESM/worker break entangled with
  pdfdataextract; exploit is self-inflicted (user's own PDF, text extraction)

Net: audit 154 -> 0 active (6 crit + 59/61 high removed via deps/overrides/bumps).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
…rrides

The `path-to-regexp: >=0.1.13` override forced express@4.21.2 onto
path-to-regexp@8.x (incompatible API) -> server crashed at boot
("pathRegexp is not a function" registering routes). Also `lodash: >=4.18.0`
was a bogus range (latest is 4.17.21).

- replace blanket override with scoped `express>path-to-regexp: 0.1.13` (patched
  AND keeps the 0.1.x function API express needs; other consumers keep 8.x)
- remove the invalid lodash override

Verified: `pnpm audit` exit 0, build:server ok, and the server BOOTS
(MongoDB + "Server started") — the Docker build alone didn't catch the runtime
crash, so this was boot-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
Broaden the remember instruction (marker + native tool) so the model also saves
self-facts it states or invents in the moment — its pet's name, family, where it
lives, backstory — not just facts about the user. Keeps the character consistent
across chats (these get stored per user+character and recalled via RAG).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
… screen)

- My AI card now opens an owned-character profile (/mine/:id): big cover +
  gallery, click any image for fullscreen (settingStore.showImage), Chat + Edit +
  Back buttons. Reuses the Discover profile styling.
- startChat(char, navigate, {forceNew?}) helper: resume the most-recent chat or
  create one, then go straight to /chat/:id. Replaces per-character
  /chats/create/:id navigations across the character views (no more confusing
  create-chat screen).
- ChatList: per-chat inline rename (pencil -> editChat) — replaces the only
  feature the create-chat screen offered (naming the chat).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
…My AI)

Wrap the Discover profile cover + gallery images in buttons that open the
fullscreen viewer (settingStore.showImage), matching the My AI profile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
- Every character carries an imageSeed (loaded or freshly generated). The editor's
  image generation (cover + gallery) sends this seed so all generated images keep
  a consistent base look — good for building a coherent LoRA reference set.
- Chat image generation omits the seed entirely, so chat images vary (the LoRA
  provides identity). generateImage now uses opts.seed (editor-supplied) instead
  of reading character.imageSeed, so chat never locks it.
- Editor UI: a "Base look seed" row with the value + a Dice "Reroll" button and a
  warning that rerolling changes the base appearance (saved images unaffected).
- Threaded seed: editor -> generateAvatar -> characterStore.generateAvatar ->
  generateImageWithPrompt -> POST /character/image -> generateImage. Persisted via
  the character create/update mapping + FormData.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
… seed)

With the locked base-look seed, generating again with the same Appearance prompt
yields the same image. Added a note under the gallery actions telling users to
vary the Appearance prompt (pose/outfit/setting) between generations for varied
reference shots while keeping the core looks consistent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
rondlite and others added 25 commits June 27, 2026 01:30
Wire isPresent into the PriorityGate singleton so the gate can drop
requests from disconnected clients. Start/stop the presence-refresh
interval in startQueue(). Mark presence on socket open and user login;
clear on socket close, error, and user logout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
…nect

Two paths charged credits before entering the gate. When ClientGoneError
is thrown (client gone before generation), image generation now refunds
IMAGE_COST (logged-in users only) and guidance refunds 10 credits.
IMAGE_COST is now exported from srv/image/index.ts and imported by the
image route instead of being redefined locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXNb2Kcdayr1ZvUpy5a3nA
Fixes the PR format gate (prettier --check) that blocked merge to trunk.
Formatting only — no behaviour changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 232378dddf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread srv/api/match.ts
Comment thread srv/api/character.ts Outdated
Comment thread srv/api/match.ts
rondlite and others added 3 commits June 29, 2026 22:16
- match clone: reset published/publishedAt/publishRewarded/moderation/reportCount
  so a cloned copy of another user's published template isn't silently relisted
  as a public, unmoderated template.
- gallery add: take a published character private for review when a new image is
  added (mirrors the edit path) so unmoderated gallery content can't go live.
- getMatch: apply the premium gate (like discover/getMatches) so guests and free
  users can't pull a premium template by direct ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
setAvatarFile(file) tripped the Setter's `U extends Function` overload branch
under CI's TypeScript. Use the functional-updater form. Surfaced now that the
prettier gate passes and CI reaches the typecheck step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Align the test suite with the rewrite:
- resolve-scenario: scenario books are ignored (feature disabled)
- progression: unknown archetype id returns no steps (no fallback)
- prompt: remove keyword memory-book tests (feature removed)
- refresh snapshots for CHARLUV_SAFEGUARD_PROMPT and dropped memory entries

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rondlite
rondlite merged commit 6c49341 into trunk Jun 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant